/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    nGenEx.lib
    FILE:         Solid.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    Classes for rendering solid meshes of polygons
*/

#include "MemDebug.h"
#include "Solid.h"
#include "Scene.h"
#include "Bitmap.h"
#include "DataLoader.h"
#include "Light.h"
#include "Shadow.h"
#include "Projector.h"
#include "OPCODE.h"

#ifdef for
#undef for
#endif

void  Print(const char* fmt, ...);

// +--------------------------------------------------------------------+

static bool use_collision_detection = true;

bool  Solid::IsCollisionEnabled()      { return use_collision_detection; }
void  Solid::EnableCollision(bool e)   { use_collision_detection = e;    }

// +--------------------------------------------------------------------+

Opcode::AABBTreeCollider   opcode_collider;

class OPCODE_data
{
public:
    OPCODE_data(Surface* s) {
        bool status = false;

        if (s) {
            using namespace Opcode;
            opcode_collider.SetFirstContact(true);

            npolys   = s->NumPolys();
            nverts   = s->NumVerts();
            ntris    = s->NumIndices() / 3;

            locs = new(__FILE__,__LINE__) IcePoint[nverts];
            tris = new(__FILE__,__LINE__) IndexedTriangle[ntris];

            if (locs && tris) {
                int i, n = 0;

                for (i = 0; i < nverts; i++) {
                    IcePoint* p = locs + i;
                    Vec3*     v = s->GetVertexSet()->loc + i;

                    p->Set(v->x, v->y, v->z);
                }

                for (i = 0; i < npolys; i++) {
                    Poly* p = s->GetPolys() + i;

                    if (p->nverts == 3) {
                        IndexedTriangle& t = tris[n++];

                        t.mVRef[0] = p->verts[0];
                        t.mVRef[1] = p->verts[2];
                        t.mVRef[2] = p->verts[1];
                    }
                    else {
                        IndexedTriangle& t1 = tris[n++];
                        IndexedTriangle& t2 = tris[n++];

                        t1.mVRef[0] = p->verts[0];
                        t1.mVRef[1] = p->verts[2];
                        t1.mVRef[2] = p->verts[1];

                        t2.mVRef[0] = p->verts[0];
                        t2.mVRef[1] = p->verts[3];
                        t2.mVRef[2] = p->verts[2];
                    }
                }

                mesh.SetNbVertices(nverts);
                mesh.SetNbTriangles(ntris);
                mesh.SetPointers(tris, locs);

                OPCODECREATE creator;
                creator.mIMesh = &mesh;
                status = model.Build(creator);
            }
        }
        else {
            tris   = 0;
            locs   = 0;
            npolys = 0;
            nverts = 0;
            ntris  = 0;
        }
    }

    ~OPCODE_data() {
        delete [] tris;
        delete [] locs;
    }

    Opcode::Model           model;
    Opcode::MeshInterface   mesh;
    IndexedTriangle*        tris;
    IcePoint*               locs;
    int                     npolys;
    int                     nverts;
    int                     ntris;
};

// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+

Solid::Solid()
    : model(0), own_model(1), roll(0.0f), pitch(0.0f), yaw(0.0f), intersection_poly(0)
{
    shadow = true;
    sprintf_s(name, "Solid %d", id);
}

// +--------------------------------------------------------------------+

Solid::~Solid()
{
    if (own_model)
    delete model;

    shadows.destroy();
}

// +--------------------------------------------------------------------+

void
Solid::Update()
{
}

// +--------------------------------------------------------------------+

void
Solid::SetOrientation(const Matrix& o)
{
    orientation = o;
}

void
Solid::SetLuminous(bool l)
{
    luminous = l;

    if (model && luminous) {
        model->luminous = luminous;

        ListIter<Material> iter = model->GetMaterials();

        while (++iter) {
            Material* mtl = iter.value();

            mtl->Ka = Color::Black;
            mtl->Kd = Color::Black;
            mtl->Ks = Color::Black;
            mtl->Ke = Color::White;

            if (mtl->tex_diffuse && !mtl->tex_emissive)
            mtl->tex_emissive = mtl->tex_diffuse;
        }

        ListIter<Surface> s_iter = model->GetSurfaces();
        while (++s_iter) {
            Surface*    surface  = s_iter.value();
            VertexSet*  vset     = surface->GetVertexSet();

            for (int i = 0; i < vset->nverts; i++) {
                vset->diffuse[i]  = Color::White.Value();
                vset->specular[i] = Color::Black.Value();
            }
        }
    }
}

// +--------------------------------------------------------------------+

void
Solid::SetOrientation(const Solid& match)
{
    if (!model || infinite)
    return;

    // copy the orientation matrix from the solid we are matching:
    orientation = match.Orientation();
}

// +--------------------------------------------------------------------+

void
Solid::Render(Video* video, DWORD flags)
{
    if (flags & RENDER_ADDITIVE)
    return;

    if (video && model && model->NumPolys()) {
        DWORD blend_modes = Video::BLEND_SOLID;

        if (flags == RENDER_ALPHA)
        blend_modes = Video::BLEND_ALPHA | Video::BLEND_ADDITIVE;

        video->DrawSolid(this, blend_modes);
    }
}

// +--------------------------------------------------------------------+

void
Solid::SelectDetail(Projector* p)
{
}

// +--------------------------------------------------------------------+

void
Solid::ProjectScreenRect(Projector* p)
{
    if (model && p) {
        Point tmp = loc;
        p->Transform(tmp);

        if (tmp.z > 1) {
            int l =  2000;
            int r = -2000;
            int t =  2000;
            int b = -2000;

            for (int i = 0; i < 6; i++) {
                Point extent;

                if (i < 2)
                extent.x = model->extents[i];

                else if (i < 4)
                extent.y = model->extents[i];

                else
                extent.z = model->extents[i];

                extent = extent * orientation + loc;

                p->Transform(extent);
                p->Project(extent);

                if (extent.x < l) l = (int) extent.x;
                if (extent.x > r) r = (int) extent.x;
                if (extent.y < t) t = (int) extent.y;
                if (extent.y > b) b = (int) extent.y;
            }

            screen_rect.x = l;
            screen_rect.y = t;
            screen_rect.w = r-l;
            screen_rect.h = b-t;
            return;
        }
    }

    screen_rect.x = 2000;
    screen_rect.y = 2000;
    screen_rect.w = 0;
    screen_rect.h = 0;
}

// +--------------------------------------------------------------------+
// Polygon Interference Detection:

int
Solid::CollidesWith(Graphic& o)
{
    Vec3 delta_loc = Location() - o.Location();

    // bounding spheres test:
    if (delta_loc.length() > Radius() + o.Radius())
    return 0;

    // possible collision, but no further refinement can be done:
    if (!o.IsSolid())
    return 1;

    Solid& s = (Solid&) o;

    // use the OPCODE library to check for polygon interference:
    if (model && s.model) {
        using namespace Opcode;

        bool contact = false;

        // first, reverse the orientation matrices for OPCODE:
        Matrix m1 = orientation;
        Matrix m2 = s.orientation;

        Matrix4x4   world0;
        Matrix4x4   world1;

        world0.m[0][0]  = (float) m1.elem[0][0];
        world0.m[0][1]  = (float) m1.elem[0][1];
        world0.m[0][2]  = (float) m1.elem[0][2];
        world0.m[0][3]  = 0.0f;

        world0.m[1][0]  = (float) m1.elem[1][0];
        world0.m[1][1]  = (float) m1.elem[1][1];
        world0.m[1][2]  = (float) m1.elem[1][2];
        world0.m[1][3]  = 0.0f;

        world0.m[2][0]  = (float) m1.elem[2][0];
        world0.m[2][1]  = (float) m1.elem[2][1];
        world0.m[2][2]  = (float) m1.elem[2][2];
        world0.m[2][3]  = 0.0f;

        world0.m[3][0]  = (float) Location().x;
        world0.m[3][1]  = (float) Location().y;
        world0.m[3][2]  = (float) Location().z;
        world0.m[3][3]  = 1.0f;

        world1.m[0][0]  = (float) m2.elem[0][0];
        world1.m[0][1]  = (float) m2.elem[1][0];
        world1.m[0][2]  = (float) m2.elem[2][0];
        world1.m[0][3]  = 0.0f;

        world1.m[1][0]  = (float) m2.elem[0][1];
        world1.m[1][1]  = (float) m2.elem[1][1];
        world1.m[1][2]  = (float) m2.elem[2][1];
        world1.m[1][3]  = 0.0f;

        world1.m[2][0]  = (float) m2.elem[0][2];
        world1.m[2][1]  = (float) m2.elem[1][2];
        world1.m[2][2]  = (float) m2.elem[2][2];
        world1.m[2][3]  = 0.0f;

        world1.m[3][0]  = (float) s.Location().x;
        world1.m[3][1]  = (float) s.Location().y;
        world1.m[3][2]  = (float) s.Location().z;
        world1.m[3][3]  = 1.0f;

        ListIter<Surface> s1_iter = model->surfaces;
        while (++s1_iter && !contact) {
            Surface* s1 = s1_iter.value();

            ListIter<Surface> s2_iter = s.model->surfaces;
            while (++s2_iter && !contact) {
                Surface* s2 = s2_iter.value();

                if (s1->opcode && s2->opcode) {
                    BVTCache bvt;
                    bvt.Model0 = &s1->opcode->model;
                    bvt.Model1 = &s2->opcode->model;

                    if (opcode_collider.Collide(bvt, &world0, &world1))
                    if (opcode_collider.GetContactStatus() != 0)
                    contact = true;
                }
            }
        }

        return contact;
    }


    return 1;
}

// +--------------------------------------------------------------------+
// Find the intersection of the ray (Q + w*len) with the solid.
// If the ray intersects a polygon of the solid, place the intersection
// point in ipt, and return 1.  Otherwise, return 0.

int
Solid::CheckRayIntersection(Point Q, Point w, double len, Point& ipt,
bool treat_translucent_polys_as_solid)
{
    int impact        = 0;

    if (!model || model->npolys < 1)
    return impact;

    // check right angle spherical distance:
    Point  d0 = loc - Q;
    Point  d1 = d0.cross(w);
    double dlen = d1.length();          // distance of point from line

    if (dlen > radius)                  // clean miss
    return 0;                        // (no impact)

    // possible collision course...

    /**********************************


        /--- + leading_edge = Q + w * len
        /    /  \
    delta2 /    delta 0
    /    /      \
    /    *........x <- solid location
    /    /
    /    / delta1
/--- Q                  * = closest point


************************************/

    // find the point on the ray that is closest
    // to the solid's location:
    Point closest = Q + w * (d0 * w);

    // find the leading edge, and it's distance from the location:
    Point  leading_edge  = Q + w*len;
    Point  leading_delta = leading_edge - loc;
    double leading_dist  = leading_delta.length();

    // if the leading edge is not within the bounding sphere,
    if (leading_dist > radius) {
        // check to see if the closest point is between the
        // ray's endpoints:
        Point delta1 = closest      - Q;
        Point delta2 = leading_edge - Q; // this is w*len

        // if the closest point is not between the leading edge
        // and the origin, this ray does not intersect:
        if (delta1 * delta2 < 0 || delta1.length() > len) {
            return 0;
        }
    }

    // probable hit at this point...

    // if not active, that's good enough:
    if (GetScene() == 0) {
        ipt = closest;
        return 1;
    }

    // transform ray into object space:
    Matrix xform(Orientation());

    Vec3 tmp = w;

    w.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2));
    w.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2));
    w.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2));

    tmp = Q-loc;

    Q.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2));
    Q.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2));
    Q.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2));

    double min = len;
    intersection_poly = 0;

    // check each polygon:
    ListIter<Surface> iter = model->surfaces;
    while (++iter) {
        Surface* s = iter.value();
        Poly*    p = s->GetPolys();

        for (int i = 0; i < s->NumPolys(); i++) {
            if (!treat_translucent_polys_as_solid && p->material && !p->material->IsSolid()) {
                p++;
                continue;
            }

            Point  v = p->plane.normal;
            double d = p->plane.distance;

            double denom = w*v;

            if (denom < -1.0e-5) {
                Point  P    = v * d;
                double ilen = ((P-Q)*v)/denom;

                if (ilen > 0 && ilen < min) {
                    Point intersect = Q + w * ilen;

                    if (p->Contains(intersect)) {
                        intersection_poly = p;
                        ipt               = intersect;
                        min               = ilen;
                        impact            = 1;
                    }
                }
            }

            p++;
        }
    }

    // xform impact point back into world coordinates:

    if (impact) {
        ipt = (ipt * Orientation()) + loc;
    }

    return impact;
}

// +--------------------------------------------------------------------+

void
Solid::ClearModel()
{
    if (own_model && model) {
        delete model;
        model = 0;
    }

    radius = 0.0f;
}

// +--------------------------------------------------------------------+

void
Solid::UseModel(Model* m)
{
    // get rid of the existing model:
    ClearModel();

    // point to the new model:
    own_model = 0;
    model     = m;
    radius    = m->radius;
}

// +--------------------------------------------------------------------+

bool
Solid::Load(const char* mag_file, double scale)
{
    // get ready to load, delete existing model:
    ClearModel();

    // loading our own copy, so we own the model:
    model     = new(__FILE__,__LINE__) Model;
    own_model = 1;

    // now load the model:
    if (model->Load(mag_file, scale)) {
        radius = model->radius;
        strncpy_s(name, model->name, sizeof(name));
        return true;
    }

    // load failed:
    ClearModel();
    return false;
}

bool
Solid::Load(ModelFile* mod_file, double scale)
{
    // get ready to load, delete existing model:
    ClearModel();

    // loading our own copy, so we own the model:
    model     = new(__FILE__,__LINE__) Model;
    own_model = 1;

    // now load the model:
    if (model->Load(mod_file, scale)) {
        radius = model->radius;
        return true;
    }

    // load failed:
    ClearModel();
    return false;
}

bool
Solid::Rescale(double scale)
{
    if (!own_model || !model)
    return false;

    radius = 0;

    ListIter<Surface> iter = model->GetSurfaces();
    while (++iter) {
        Surface* s = iter.value();

        for (int v = 0; v < s->NumVerts(); v++) {
            s->vertex_set->loc[v]   *= (float) scale;
            s->vloc[v]              *= (float) scale;

            float lvi = s->vloc[v].length();
            if (lvi > radius)
            radius = lvi;
        }
    }

    model->radius = radius;

    InvalidateSurfaceData();

    return true;
}

void
Solid::CreateShadows(int nlights)
{
    while (shadows.size() < nlights) {
        shadows.append(new(__FILE__,__LINE__) Shadow(this));
    }
}

void
Solid::UpdateShadows(List<Light>& lights)
{
    List<Light>       active_lights;
    ListIter<Light>   iter = lights;

    while (++iter) {
        Light* light = iter.value();

        if (light->IsActive() && light->CastsShadow()) {
            double distance  = Point(Location() - light->Location()).length();
            double intensity = light->Intensity();

            if (light->Type() == Light::LIGHT_POINT) {
                if (intensity / distance > 1)
                active_lights.append(light);
            }

            else if (light->Type() == Light::LIGHT_DIRECTIONAL) {
                if (intensity > 0.65)
                active_lights.insert(light);
            }
        }
    }

    iter.attach(active_lights);

    while (++iter) {
        Light* light = iter.value();
        int    index = iter.index();

        if (index < shadows.size()) {
            shadows[index]->Update(light);
        }
    }
}

// +--------------------------------------------------------------------+

void
Solid::DeletePrivateData()
{
    if (model)
    model->DeletePrivateData();
}

// +--------------------------------------------------------------------+

void
Solid::InvalidateSurfaceData()
{
    if (!model)
    return;

    bool invalidate = model->IsDynamic();

    ListIter<Surface> iter = model->GetSurfaces();
    while (++iter) {
        Surface* s = iter.value();
        VideoPrivateData* vpd = s->GetVideoPrivateData();

        if (vpd) {
            if (invalidate) {
                vpd->Invalidate();
            }
            else {
                delete vpd;
                s->SetVideoPrivateData(0);
            }
        }
    }
}

void
Solid::InvalidateSegmentData()
{
    if (!model)
    return;

    bool invalidate = model->IsDynamic();

    ListIter<Surface> iter = model->GetSurfaces();
    while (++iter) {
        Surface* s = iter.value();

        ListIter<Segment> seg_iter = s->GetSegments();
        while (++seg_iter) {
            Segment* segment = seg_iter.value();
            VideoPrivateData* vpd = segment->GetVideoPrivateData();

            if (vpd) {
                if (invalidate) {
                    vpd->Invalidate();
                }
                else {
                    delete vpd;
                    segment->SetVideoPrivateData(0);
                }
            }
        }
    }
}

// +--------------------------------------------------------------------+

bool
Solid::IsDynamic() const
{
    if (model)
    return model->IsDynamic();

    return false;
}

void
Solid::SetDynamic(bool d)
{
    if (model && own_model)
    model->SetDynamic(d);
}

// +--------------------------------------------------------------------+

void
Solid::GetAllTextures(List<Bitmap>& textures)
{
    if (model)
    model->GetAllTextures(textures);
}

void
Model::GetAllTextures(List<Bitmap>& textures)
{
    ListIter<Material> m_iter = materials;
    while (++m_iter) {
        Material* m = m_iter.value();

        if (m->tex_diffuse && !textures.contains(m->tex_diffuse))
        textures.append(m->tex_diffuse);

        if (m->tex_specular && !textures.contains(m->tex_specular))
        textures.append(m->tex_specular);

        if (m->tex_emissive && !textures.contains(m->tex_emissive))
        textures.append(m->tex_emissive);

        if (m->tex_bumpmap && !textures.contains(m->tex_bumpmap))
        textures.append(m->tex_bumpmap);

        if (m->tex_detail && !textures.contains(m->tex_detail))
        textures.append(m->tex_detail);
    }
}

// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+

Model::Model()
    : nverts(0), npolys(0), radius(0), luminous(false), dynamic(false)
{ 
    ZeroMemory(name, sizeof(name));
}

Model::Model(const Model& m)
    : nverts(0), npolys(0), radius(0), luminous(false), dynamic(false)
{
    operator=(m);
}

// +--------------------------------------------------------------------+

Model::~Model()
{
    surfaces.destroy();
    materials.destroy();
}

Model&
Model::operator = (const Model& m)
{
    if (this != &m) {
        surfaces.destroy();
        materials.destroy();

        CopyMemory(name, m.name, Solid::NAMELEN);

        nverts      = m.nverts;
        npolys      = m.npolys;
        radius      = m.radius;
        luminous    = m.luminous;
        dynamic     = m.dynamic;

        Model* pmod = (Model*) &m;

        ListIter<Material> m_iter = pmod->materials;
        while (++m_iter) {
            Material* matl1 = m_iter.value();
            Material* matl2 = new(__FILE__,__LINE__) Material;

            CopyMemory(matl2, matl1, sizeof(Material));
            matl2->thumbnail = 0;

            materials.append(matl2);
        }

        ListIter<Surface> s_iter = pmod->surfaces;
        while (++s_iter) {
            Surface* surf1 = s_iter.value();
            Surface* surf2 = new(__FILE__,__LINE__) Surface;

            surf2->Copy(*surf1, this);
            surfaces.append(surf2);
        }
    }

    return *this;
}

// +--------------------------------------------------------------------+

int
Model::NumSegments() const
{
    int nsegments = 0;

    for (int i = 0; i < surfaces.size(); i++) {
        const Surface* s = surfaces[i];
        nsegments += s->NumSegments();
    }

    return nsegments;
}

// +--------------------------------------------------------------------+

inline bool Collinear(const double* a, const double* b, const double* c)
{
    Point ab(b[0]-a[0], b[1]-a[1], b[2]-a[2]);
    Point ac(c[0]-a[0], c[1]-a[1], c[2]-a[2]);
    Point cross = ab.cross(ac);
    return (cross.length() == 0);
}

struct HomogenousPlane
{
    double distance;
    double normal_x;
    double normal_y;
    double normal_z;
    double normal_w;
};

static void LoadPlane(Plane& p, DataLoader* l, BYTE*& fp)
{
    HomogenousPlane tmp;
    l->fread(&tmp, sizeof(HomogenousPlane), 1, fp);
}

static void LoadFlags(LPDWORD flags, DataLoader* l, BYTE*& fp)
{
    DWORD magic_flags;
    l->fread(&magic_flags, sizeof(DWORD), 1, fp);

    /** OLD MAGIC FLAGS
enum { FLAT_SHADED =   1,
        LUMINOUS    =   2,
        TRANSLUCENT =   4,  \\ must swap
        CHROMAKEY   =   8,  // these two
        FOREGROUND  =  16,  -- not used
        WIREFRAME   =  32,  -- not used
        SPECULAR1   =  64,
        SPECULAR2   = 128  };
***/

    const DWORD magic_mask = 0x0fc3;

    *flags = magic_flags & magic_mask;
}

// +--------------------------------------------------------------------+

bool
Model::Load(const char* mag_file, double scale)
{
    BYTE*       block;
    DataLoader* loader = DataLoader::GetLoader();
    bool        result = false;

    radius      = 0.0f;
    extents[0]  = 0.0f;
    extents[1]  = 0.0f;
    extents[2]  = 0.0f;
    extents[3]  = 0.0f;
    extents[4]  = 0.0f;
    extents[5]  = 0.0f;

    if (!loader) {
        Print("MAG Open Failed:  no data loader for file '%s'\n", mag_file);
        return result;
    }

    int         size   = loader->LoadBuffer(mag_file, block);
    BYTE*       fp     = block;

    // check MAG file:
    if (!size) {
        Print("MAG Open Failed:  could not open file '%s'\n", mag_file);
        return result;
    }

    strncpy_s(name, mag_file, 31);
    name[31] = 0;

    char file_id[5];
    CopyMemory(file_id, block, 4);
    file_id[4] = '\0';
    int version = 1;

    if (!strcmp(file_id, "MAG6")) {
        version = 6;
    }
    else if (!strcmp(file_id, "MAG5")) {
        version = 5;
    }
    else if (!strcmp(file_id, "MAG4")) {
        version = 4;
    }
    else {
        Print("MAG Open Failed:  File '%s' Invalid file type '%s'\n", mag_file, file_id);
        loader->ReleaseBuffer(block);
        return result;
    }

    // get ready to load, delete existing model:
    surfaces.destroy();
    materials.destroy();
    nverts   = 0;
    npolys   = 0;

    // now load the model:
    switch (version) {
    case 4:
    case 5:
        result = LoadMag5(block, size, scale);
        break;

    case 6:
        result = LoadMag6(block, size, scale);
        break;

    default:
        break;
    }

    loader->ReleaseBuffer(block);
    return result;
}

// +--------------------------------------------------------------------+

bool
Model::Load(ModelFile* mod_file, double scale)
{
    if (mod_file) {
        return mod_file->Load(this, scale);
    }

    return false;
}

// +--------------------------------------------------------------------+

static int mcomp(const void* a, const void* b)
{
    Poly* pa = (Poly*) a;
    Poly* pb = (Poly*) b;

    if (pa->sortval == pb->sortval)
    return 0;

    if (pa->sortval < pb->sortval)
    return 1;

    return -1;
}

bool
Model::LoadMag5(BYTE* block, int len, double scale)
{
    bool        result = false;

    DataLoader* loader = DataLoader::GetLoader();
    BYTE*       fp     = block + 4;
    int         ntex   = 0;
    int         nsurfs = 0;

    loader->fread(&ntex,   sizeof(ntex),   1, fp);
    loader->fread(&nsurfs, sizeof(nsurfs), 1, fp);

    // create a default gray material:
    Material*   mtl = new Material;

    if (mtl) {
        mtl->Ka = Color::LightGray;
        mtl->Kd = Color::LightGray;
        mtl->Ks = ColorValue(0.2f,0.2f,0.2f);
        mtl->power = 20.0f;

        mtl->ambient_value  = 1.0f;
        mtl->ambient_color  = Color::LightGray;
        mtl->diffuse_value  = 1.0f;
        mtl->diffuse_color  = Color::LightGray;
        mtl->specular_value = 0.2f;
        mtl->specular_color = Color::White;
        strcpy_s(mtl->name, "(default)");

        materials.append(mtl);
    }

    // read texture list:
    for (int i = 0; i < ntex; i++) {
        mtl = new(__FILE__,__LINE__) Material;
        char        tname[32];

        if (mtl) {
            mtl->Ka = ColorValue(0.5f,0.5f,0.5f);
            mtl->Kd = ColorValue(1.0f,1.0f,1.0f);
            mtl->Ks = ColorValue(0.2f,0.2f,0.2f);
            mtl->power = 20.0f;

            mtl->ambient_value  = 1.0f;
            mtl->ambient_color  = Color::Gray;
            mtl->diffuse_value  = 1.0f;
            mtl->diffuse_color  = Color::White;
            mtl->specular_value = 0.2f;
            mtl->specular_color = Color::White;

            loader->fread(tname, 32, 1, fp);
            loader->LoadTexture(tname, mtl->tex_diffuse, Bitmap::BMP_SOLID, true);
            strcpy_s(mtl->name, tname);

            char* dot = strrchr(mtl->name, '.');
            if (dot)
            *dot = 0;

            char* plus = strrchr(mtl->name, '+');
            if (plus)
            *plus = 0;

            materials.append(mtl);
        }
    }


    loader->fread(&nverts, 4, 1, fp);
    loader->fread(&npolys, 4, 1, fp);

    // plan on creating four verts per poly:
    int mag_nverts = nverts;
    int next_vert  = nverts;

    nverts      = npolys * 4;

    Surface*    s    = new(__FILE__,__LINE__) Surface;
    VertexSet*  vset = 0;

    if (s) {
        strcpy_s(s->name, "default");

        s->model       = this;
        s->vertex_set  = new(__FILE__,__LINE__) VertexSet(nverts);
        s->vloc        = new(__FILE__,__LINE__) Vec3[nverts];

        ZeroMemory(s->vertex_set->loc,      nverts * sizeof(Vec3));
        ZeroMemory(s->vertex_set->diffuse,  nverts * sizeof(DWORD));
        ZeroMemory(s->vertex_set->specular, nverts * sizeof(DWORD));
        ZeroMemory(s->vertex_set->tu,       nverts * sizeof(float));
        ZeroMemory(s->vertex_set->tv,       nverts * sizeof(float));
        ZeroMemory(s->vertex_set->rw,       nverts * sizeof(float));
        ZeroMemory(s->vloc,                 nverts * sizeof(Vec3));

        s->npolys      = npolys;
        s->polys       = new(__FILE__,__LINE__) Poly[npolys];

        ZeroMemory(s->polys, sizeof(Poly) * npolys);
        surfaces.append(s);

        vset = s->vertex_set;

        int v;
        // read vertex set:
        for (v = 0; v < mag_nverts; v++) {
            Vec3 vert, norm;
            DWORD vstate;

            loader->fread(&vert,    sizeof(Vec3), 1, fp);
            loader->fread(&norm,    sizeof(Vec3), 1, fp);
            loader->fread(&vstate,  sizeof(DWORD), 1, fp);

            vert.SwapYZ();
            vert *= (float) scale;

            vset->loc[v]      = vert;
            vset->nrm[v]      = norm;

            double d = vert.length();
            if (d > radius)
            radius = (float) d;

            if (vert.x > extents[0])   extents[0] = vert.x;
            if (vert.x < extents[1])   extents[1] = vert.x;
            if (vert.y > extents[2])   extents[2] = vert.y;
            if (vert.y < extents[3])   extents[3] = vert.y;
            if (vert.z > extents[4])   extents[4] = vert.z;
            if (vert.z < extents[5])   extents[5] = vert.z;
        }

        while (v < nverts)
        vset->nrm[v++] = Vec3(1,0,0);

        // read polys:
        Vec3  dummy_center;
        DWORD dummy_flags;
        DWORD dummy_color;
        Plane dummy_plane;
        int   texture_num;
        int   poly_nverts;
        int   vert_index_buffer[32];
        float texture_index_buffer[32];

        for (int n = 0; n < npolys; n++) {
            Poly& poly = s->polys[n];
            poly.vertex_set  = vset;

            loader->fread(&dummy_flags,  sizeof(DWORD), 1, fp);
            loader->fread(&dummy_center,  sizeof(Vec3),  1, fp);

            LoadPlane(dummy_plane, loader, fp);

            loader->fread(&dummy_color,  sizeof(DWORD), 1, fp);
            loader->fread(&texture_num,   sizeof(int),   1, fp);

            if (texture_num >= 0 && texture_num < ntex) {
                int mtl_num   = texture_num + 1;
                poly.material = materials[mtl_num];
                poly.sortval  = texture_num;

                bool flag_translucent = (dummy_flags & 0x04) ? true : false;
                bool flag_transparent = (dummy_flags & 0x08) ? true : false;

                // luminous
                if (dummy_flags & 2) {
                    mtl = materials[mtl_num];

                    mtl->Ka = ColorValue(0,0,0,0);
                    mtl->Kd = ColorValue(0,0,0,0);
                    mtl->Ks = ColorValue(0,0,0,0);
                    mtl->Ke = ColorValue(1,1,1,1);

                    mtl->tex_emissive = mtl->tex_diffuse;
                }

                // glowing (additive)
                if (flag_translucent && flag_transparent)
                materials[mtl_num]->blend = Material::MTL_ADDITIVE;

                // translucent (alpha)
                else if (flag_translucent)
                materials[mtl_num]->blend = Material::MTL_TRANSLUCENT;

                // transparent (just use alpha for this)
                else if (flag_transparent)
                materials[mtl_num]->blend = Material::MTL_TRANSLUCENT;
            }
            else {
                poly.material = materials.first();
                poly.sortval  = 1000;
            }

            // hack: store flat shaded flag in unused visible byte
            poly.visible = (BYTE) (dummy_flags & 1);

            loader->fread(&poly_nverts,           sizeof(int),   1, fp);
            loader->fread(vert_index_buffer,      sizeof(int),   poly_nverts, fp);

            if (poly_nverts == 3)
            s->nindices += 3;

            else if (poly_nverts == 4)
            s->nindices += 6;

            poly.nverts = poly_nverts;
            for (int vi = 0; vi < poly_nverts; vi++) {
                v = vert_index_buffer[vi];

                if (vset->rw[v] > 0) {
                    vset->CopyVertex(next_vert, v);
                    v = next_vert++;
                }

                vset->rw[v] = 1;
                poly.verts[vi] = v;
            }
            
            loader->fread(texture_index_buffer, sizeof(float), poly_nverts, fp); // tu's
            for (int vi = 0; vi < poly_nverts; vi++) {
                v = poly.verts[vi];
                vset->tu[v] = texture_index_buffer[vi];
            }

            loader->fread(texture_index_buffer, sizeof(float), poly_nverts, fp); // tv's
            for (int vi = 0; vi < poly_nverts; vi++) {
                v = poly.verts[vi];
                vset->tv[v] = texture_index_buffer[vi];
            }

            fp += 16;
        }

        // pass 2 (adjust vertex normals for flat polys):
        for (int n = 0; n < npolys; n++) {
            Poly& poly  = s->polys[n];
            poly.plane  = Plane(vset->loc[poly.verts[0]],
            vset->loc[poly.verts[2]],
            vset->loc[poly.verts[1]]);

            // hack: retrieve flat shaded flag from unused visible byte
            if (poly.visible) {
                poly_nverts = poly.nverts;

                for (int vi = 0; vi < poly_nverts; vi++) {
                    v = poly.verts[vi];
                    vset->nrm[v] = poly.plane.normal;
                }
            }
        }

        // sort the polys by material index:
        qsort((void*) s->polys, s->npolys, sizeof(Poly), mcomp);

        // then assign them to cohesive segments:
        Segment* segment = 0;

        for (int n = 0; n < npolys; n++) {
            if (segment && segment->material == s->polys[n].material) {
                segment->npolys++;
            }
            else {
                segment = 0;
            }

            if (!segment) {
                segment = new(__FILE__,__LINE__) Segment;

                segment->npolys   = 1;
                segment->polys    = &s->polys[n];
                segment->material = segment->polys->material;
                segment->model    = this;

                s->segments.append(segment);
            }
        }

        s->BuildHull();

        result = nverts && npolys;
    }

    return result;
}

// +--------------------------------------------------------------------+

struct MaterialMag6 {
    char        name[Material::NAMELEN];
    char        shader[Material::NAMELEN];
    float       power;      // highlight sharpness (big=shiny)
    float       brilliance; // diffuse power function
    float       bump;       // bump level (0=none)
    DWORD       blend;      // alpha blend type
    bool        shadow;     // material casts shadow
    bool        luminous;   // verts have their own lighting

    Color       ambient_color;
    Color       diffuse_color;
    Color       specular_color;
    Color       emissive_color;

    float       ambient_value;
    float       diffuse_value;
    float       specular_value;
    float       emissive_value;

    BYTE        tex_diffuse;
    BYTE        tex_specular;
    BYTE        tex_bumpmap;
    BYTE        tex_emissive;
};

// +--------------------------------------------------------------------+

bool
Model::LoadMag6(BYTE* block, int len, double scale)
{
    bool           result = false;

    DataLoader*    loader = DataLoader::GetLoader();
    BYTE*          fp     = block + 4;
    int            ntex   = 0;
    int            nmtls  = 0;
    int            nsurfs = 0;
    List<Bitmap>   textures;

    loader->fread(&ntex,   sizeof(ntex),   1, fp); // size of texture block
    loader->fread(&nmtls,  sizeof(nmtls),  1, fp); // number of materials
    loader->fread(&nsurfs, sizeof(nsurfs), 1, fp); // number of surfaces

    // read texture list:
    if (ntex) {
        char*          buffer      = new(__FILE__,__LINE__) char[ntex];
        char*          p           = buffer;
        Bitmap*        bmp         = 0;

        loader->fread(buffer, ntex, 1, fp);

        while (p < buffer + ntex) {
            loader->LoadTexture(p, bmp, Bitmap::BMP_SOLID, true);
            textures.append(bmp);

            p += strlen(p) + 1;
        }

        delete [] buffer;
    }

    for (int i = 0; i < nmtls; i++) {
        MaterialMag6   m6;
        Material*      mtl = new(__FILE__,__LINE__) Material;

        loader->fread(&m6, sizeof(m6), 1, fp);

        if (mtl) {
            CopyMemory(mtl->name,   m6.name,   Material::NAMELEN);
            CopyMemory(mtl->shader, m6.shader, Material::NAMELEN);

            mtl->ambient_value   = m6.ambient_value;
            mtl->ambient_color   = m6.ambient_color;
            mtl->diffuse_value   = m6.diffuse_value;
            mtl->diffuse_color   = m6.diffuse_color;
            mtl->specular_value  = m6.specular_value;
            mtl->specular_color  = m6.specular_color;
            mtl->emissive_value  = m6.emissive_value;
            mtl->emissive_color  = m6.emissive_color;

            mtl->Ka = ColorValue(mtl->ambient_color)  * mtl->ambient_value;
            mtl->Kd = ColorValue(mtl->diffuse_color)  * mtl->diffuse_value;
            mtl->Ks = ColorValue(mtl->specular_color) * mtl->specular_value;
            mtl->Ke = ColorValue(mtl->emissive_color) * mtl->emissive_value;

            mtl->power           = m6.power;
            mtl->brilliance      = m6.brilliance;
            mtl->bump            = m6.bump;
            mtl->blend           = m6.blend;
            mtl->shadow          = m6.shadow;
            mtl->luminous        = m6.luminous;

            if (m6.tex_diffuse && m6.tex_diffuse <= textures.size())
            mtl->tex_diffuse = textures[m6.tex_diffuse - 1];

            if (m6.tex_specular && m6.tex_specular <= textures.size())
            mtl->tex_specular = textures[m6.tex_specular - 1];

            if (m6.tex_emissive && m6.tex_emissive <= textures.size())
            mtl->tex_emissive = textures[m6.tex_emissive - 1];

            if (m6.tex_bumpmap && m6.tex_bumpmap <= textures.size())
            mtl->tex_bumpmap = textures[m6.tex_bumpmap - 1];

            materials.append(mtl);
        }
    }

    for (int i = 0; i < nsurfs; i++) {
        int   nverts  = 0;
        int   npolys  = 0;
        BYTE  namelen = 0;
        char  name[128];

        loader->fread(&nverts,  4, 1,       fp);
        loader->fread(&npolys,  4, 1,       fp);
        loader->fread(&namelen, 1, 1,       fp);
        loader->fread(name,     1, namelen, fp);

        Surface* surface = new(__FILE__,__LINE__) Surface;
        surface->model = this;
        surface->SetName(name);
        surface->CreateVerts(nverts);
        surface->CreatePolys(npolys);

        VertexSet*  vset  = surface->GetVertexSet();
        Poly*       polys = surface->GetPolys();

        ZeroMemory(polys, sizeof(Poly) * npolys);

        // read vertex set:
        for (int v = 0; v < nverts; v++) {
            loader->fread(&vset->loc[v],         sizeof(float), 3, fp);
            loader->fread(&vset->nrm[v],         sizeof(float), 3, fp);
            loader->fread(&vset->tu[v],          sizeof(float), 1, fp);
            loader->fread(&vset->tv[v],          sizeof(float), 1, fp);

            vset->loc[v] *= (float) scale;

            Vec3 vert = vset->loc[v];

            double d = vert.length();
            if (d > radius)
            radius = (float) d;

            if (vert.x > extents[0])   extents[0] = vert.x;
            if (vert.x < extents[1])   extents[1] = vert.x;
            if (vert.y > extents[2])   extents[2] = vert.y;
            if (vert.y < extents[3])   extents[3] = vert.y;
            if (vert.z > extents[4])   extents[4] = vert.z;
            if (vert.z < extents[5])   extents[5] = vert.z;
        }

        // read polys:
        for (int n = 0; n < npolys; n++) {
            Poly& poly           = polys[n];
            BYTE  poly_nverts    = 0;
            BYTE  material_index = 0;
            WORD  poly_verts[8];

            loader->fread(&poly_nverts,      sizeof(BYTE),  1, fp);
            loader->fread(&material_index,   sizeof(BYTE),  1, fp);
            loader->fread(&poly_verts[0],    sizeof(WORD),  poly_nverts, fp);

            if (poly_nverts >= 3) {
                poly.nverts = poly_nverts;

                for (int i = 0; i < poly_nverts; i++) {
                    poly.verts[i] = poly_verts[i];
                }
            }
            else {
                poly.sortval = 666;
            }

            if (material_index > 0) {
                poly.material = materials[material_index-1];
                poly.sortval  = material_index;
            }
            else if (materials.size()) {
                poly.material = materials.first();
                poly.sortval  = 1;
            }
            else {
                poly.sortval  = 1000;
            }

            if (poly.nverts == 3)
            surface->AddIndices(3);

            else if (poly.nverts == 4)
            surface->AddIndices(6);

            poly.vertex_set = vset;
            poly.plane = Plane(vset->loc[poly.verts[0]],
            vset->loc[poly.verts[2]],
            vset->loc[poly.verts[1]]);
        }

        // sort the polys by material index:
        qsort((void*) polys, npolys, sizeof(Poly), mcomp);

        // then assign them to cohesive segments:
        Segment* segment = 0;

        for (int n = 0; n < npolys; n++) {
            if (segment && segment->material == polys[n].material) {
                segment->npolys++;
            }
            else {
                segment = 0;
            }

            if (!segment) {
                segment = new(__FILE__,__LINE__) Segment;

                segment->npolys   = 1;
                segment->polys    = &polys[n];
                segment->material = segment->polys->material;
                segment->model    = this;

                surface->GetSegments().append(segment);
            }
        }

        surface->BuildHull();
        surfaces.append(surface);

        this->nverts += nverts;
        this->npolys += npolys;
    }


    result = nverts && npolys;
    return result;
}

void
Model::AddSurface(Surface* surface)
{
    if (surface) {
        surface->model = this;

        ListIter<Segment> iter = surface->segments;
        while (++iter) {
            Segment* segment = iter.value();
            segment->model = this;
        }

        surface->BuildHull();
        surfaces.append(surface);

        nverts += surface->NumVerts();
        npolys += surface->NumPolys();
    }
}


// +--------------------------------------------------------------------+

const Material*
Model::FindMaterial(const char* mtl_name) const
{
    if (mtl_name && *mtl_name) {
        Model* pThis = (Model*) this;

        ListIter<Material> iter = pThis->materials;
        while (++iter) {
            Material* mtl = iter.value();

            if (!strcmp(mtl->name, mtl_name))
            return mtl;
        }
    }

    return 0;
}

const Material*
Model::ReplaceMaterial(const Material* mtl)
{
    const Material* mtl_orig = 0;

    if (mtl) {
        mtl_orig = FindMaterial(mtl->name);

        if (mtl_orig) {
            int n = materials.index(mtl_orig);
            materials[n] = (Material*) mtl;

            ListIter<Surface> surf_iter = surfaces;
            while (++surf_iter) {
                Surface* surf = surf_iter.value();

                ListIter<Segment> seg_iter = surf->GetSegments();
                while (++seg_iter) {
                    Segment* segment = seg_iter.value();

                    if (segment->material == mtl_orig)
                    segment->material = (Material*) mtl;
                }
            }
        }
    }

    return mtl_orig;
}

// +--------------------------------------------------------------------+

Poly*
Model::AddPolys(int nsurf, int np, int nv)
{
    if (nsurf >= 0 && nsurf < surfaces.size())
    return surfaces[nsurf]->AddPolys(np, nv);

    ::Print("WARNING: AddPolys(%d,%d,%d) invalid surface\n", nsurf, np, nv);
    return 0;
}

// +--------------------------------------------------------------------+

void
Model::ExplodeMesh()
{
    ListIter<Surface> iter = surfaces;

    int nv = 0;
    int np = 0;

    while (++iter) {
        Surface* s = iter.value();
        s->ExplodeMesh();

        nv += s->NumVerts();
        np += s->NumPolys();
    }

    nverts = nv;
    npolys = np;
}

// +--------------------------------------------------------------------+

void
Model::OptimizeMesh()
{
    ListIter<Surface> iter = surfaces;

    int nv = 0;
    int np = 0;

    while (++iter) {
        Surface* s = iter.value();
        s->OptimizeMesh();

        nv += s->NumVerts();
        np += s->NumPolys();
    }

    nverts = nv;
    npolys = np;
}

// +--------------------------------------------------------------------+

void
Model::OptimizeMaterials()
{
    for (int i = 0; i < materials.size(); i++) {
        Material* m1 = materials[i];

        for (int n = i; n < materials.size(); n++) {
            Material* m2 = materials[n];

            // if they match, merge them:
            if (*m1 == *m2) {
                List<Poly> polys;
                SelectPolys(polys, m2);

                ListIter<Poly> iter = polys;
                while (++iter) {
                    Poly* p = iter.value();
                    p->material = m1;
                }

                // and discard the duplicate:
                materials.remove(m2);
                delete m2;
            }
        }
    }
}

void
Model::ScaleBy(double factor)
{
    ListIter<Surface> iter = surfaces;

    while (++iter) {
        Surface* s = iter.value();
        s->ScaleBy(factor);
    }
}

// +--------------------------------------------------------------------+

void
Model::Normalize()
{
    ListIter<Surface> iter = surfaces;

    while (++iter) {
        Surface* s = iter.value();
        s->Normalize();
    }
}

void
Model::SelectPolys(List<Poly>& polys, Vec3 loc)
{
    ListIter<Surface> iter = surfaces;

    while (++iter) {
        Surface* s = iter.value();
        s->SelectPolys(polys, loc);
    }
}

void
Model::SelectPolys(List<Poly>& polys, Material* m)
{
    ListIter<Surface> iter = surfaces;

    while (++iter) {
        Surface* s = iter.value();
        s->SelectPolys(polys, m);
    }
}

void
Model::ComputeTangents()
{
    ListIter<Surface> iter = surfaces;

    while (++iter) {
        Surface* s = iter.value();
        s->ComputeTangents();
    }
}

// +--------------------------------------------------------------------+

void
Model::DeletePrivateData()
{
    ListIter<Surface> iter = surfaces;
    while (++iter) {
        Surface* s = iter.value();
        VideoPrivateData* vpd = s->GetVideoPrivateData();

        if (vpd) {
            delete vpd;
            s->SetVideoPrivateData(0);
        }

        ListIter<Segment> seg_iter = s->GetSegments();
        while (++seg_iter) {
            Segment* segment = seg_iter.value();
            VideoPrivateData* vpdp = segment->video_data;

            if (vpdp) {
                delete vpdp;
                segment->video_data = 0;
            }
        }
    }
}

// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+

Surface::Surface()
    : model(0), vertex_set(0), vloc(0), nhull(0), npolys(0), nindices(0),
      polys(0), state(0), video_data(0), opcode(0)
{
    ZeroMemory(name, sizeof(name));
}

Surface::~Surface()
{
    segments.destroy();

    delete    opcode;
    delete    vertex_set;
    delete [] vloc;
    delete [] polys;
    delete    video_data;

    model = 0;
}

// +--------------------------------------------------------------------+

void
Surface::Copy(Surface& s, Model* m)
{
    segments.destroy();

    delete    opcode;
    delete    vertex_set;
    delete [] vloc;
    delete [] polys;
    delete    video_data;

    CopyMemory(name, s.name, Solid::NAMELEN);

    model          = m;
    radius         = s.radius;
    nhull          = s.nhull;
    npolys         = s.npolys;
    nindices       = s.nindices;
    state          = s.state;
    offset         = s.offset;
    orientation    = s.orientation;
    opcode         = 0;
    video_data     = 0;

    vertex_set     = s.vertex_set->Clone();

    if (nhull > 0) {
        vloc        = new(__FILE__,__LINE__) Vec3[nhull];
        CopyMemory(vloc, s.vloc, nhull * sizeof(Vec3));
    }
    else {
        vloc        = 0;
    }

    polys          = new(__FILE__,__LINE__) Poly[npolys];
    CopyMemory(polys, s.polys, npolys * sizeof(Poly));

    for (int i = 0; i < npolys; i++) {
        polys[i].vertex_set = vertex_set;
        
        if (s.polys[i].material)
        polys[i].material = (Material*) model->FindMaterial(s.polys[i].material->name);
    }

    ListIter<Segment> iter = s.segments;
    while (++iter) {
        Segment* seg1 = iter.value();
        Segment* seg2 = new(__FILE__,__LINE__) Segment;

        seg2->npolys      = seg1->npolys;
        seg2->polys       = polys + (seg1->polys - s.polys);

        if (seg2->polys[0].material)
        seg2->material    = seg2->polys[0].material;

        seg2->model       = model;
        seg2->video_data  = 0;

        segments.append(seg2);
    }
}

// +--------------------------------------------------------------------+

void
Surface::SetName(const char* n)
{
    int len = sizeof(name);

    ZeroMemory(name, len);
    strncpy_s(name, n, len-1);
}

void
Surface::SetHidden(bool b)
{
    if (b)
    state = state | HIDDEN;

    else
    state = state & ~HIDDEN;
}

void
Surface::SetLocked(bool b)
{
    if (b)
    state = state | LOCKED;

    else
    state = state & ~LOCKED;
}

void
Surface::SetSimplified(bool b)
{
    if (b)
    state = state | SIMPLE;

    else
    state = state & ~SIMPLE;
}

void
Surface::CreateVerts(int nverts)
{
    if (!vertex_set && !vloc) {
        vertex_set  = new(__FILE__,__LINE__) VertexSet(nverts);
        vloc        = new(__FILE__,__LINE__) Vec3[nverts];
    }
}

void
Surface::CreatePolys(int np)
{
    if (!polys && !npolys) {
        npolys = np;
        polys  = new(__FILE__,__LINE__) Poly[npolys];

        ZeroMemory(polys, npolys * sizeof(Poly));
    }
}

// +--------------------------------------------------------------------+

Poly*
Surface::AddPolys(int np, int nv)
{
    if (  polys && vertex_set &&
            np > 0 && np + npolys             < MAX_POLYS && 
            nv > 0 && nv + vertex_set->nverts < MAX_VERTS)
    {
        int newverts = nv + vertex_set->nverts;
        int newpolys = np + npolys;

        vertex_set->Resize(newverts, true);

        Poly* pset = new(__FILE__,__LINE__) Poly[newpolys];
        Poly* pnew = pset + npolys;

        CopyMemory(pset, polys, npolys * sizeof(Poly));
        ZeroMemory(pnew,        np     * sizeof(Poly));

        if (segments.size() > 0) {
            Segment*    seg = segments.last();
            Material*   mtl = seg->material;
            
            for (int i = 0; i < np; i++) {
                Poly* p = pnew + i;
                p->material = mtl;
            }

            seg->npolys += np;
        }
    }

    return 0;
}

// +--------------------------------------------------------------------+

void
Surface::ExplodeMesh()
{
    if (!vertex_set || vertex_set->nverts < 3)
    return;

    int i, j, v;
    int nverts = 0;

    // count max verts:
    for (i = 0; i < npolys; i++) {
        Poly* p = polys + i;
        nverts += p->nverts;
    }

    // create target vertex set:
    VertexSet*  vset = new(__FILE__,__LINE__) VertexSet(nverts);
    v = 0;

    // explode verts:
    for (i = 0; i < npolys; i++) {
        Poly* p = polys + i;
        p->vertex_set = vset;

        for (j = 0; j < p->nverts; j++) {
            int vsrc = p->verts[j];

            vset->loc[v]      = vertex_set->loc[vsrc];
            vset->nrm[v]      = vertex_set->nrm[vsrc];
            vset->tu[v]       = vertex_set->tu[vsrc];
            vset->tv[v]       = vertex_set->tv[vsrc];
            vset->rw[v]       = vertex_set->rw[vsrc];
            vset->diffuse[v]  = vertex_set->diffuse[vsrc];
            vset->specular[v] = vertex_set->specular[vsrc];

            p->verts[j]       = v++;
        }
    }

    // finalize:
    if (vset) {
        delete vertex_set;
        vertex_set = vset;
    }

    if (vloc)
    delete [] vloc;

    vloc = new(__FILE__,__LINE__) Vec3[nverts];

    ComputeTangents();
    BuildHull();
}

// +--------------------------------------------------------------------+

const double SELECT_EPSILON = 0.05;
const double SELECT_TEXTURE = 0.0001;

static bool MatchVerts(VertexSet* vset, int i, int j)
{
    double      d   = 0;
    const Vec3& vl1 = vset->loc[i];
    const Vec3& vn1 = vset->nrm[i];
    float       tu1 = vset->tu[i];
    float       tv1 = vset->tv[i];
    const Vec3& vl2 = vset->loc[j];
    const Vec3& vn2 = vset->nrm[j];
    float       tu2 = vset->tu[j];
    float       tv2 = vset->tv[j];

    d = fabs(vl1.x - vl2.x);
    if (d > SELECT_EPSILON)
    return false;

    d = fabs(vl1.y - vl2.y);
    if (d > SELECT_EPSILON)
    return false;

    d = fabs(vl1.z - vl2.z);
    if (d > SELECT_EPSILON)
    return false;

    d = fabs(vn1.x - vn2.x);
    if (d > SELECT_EPSILON)
    return false;

    d = fabs(vn1.y - vn2.y);
    if (d > SELECT_EPSILON)
    return false;

    d = fabs(vn1.z - vn2.z);
    if (d > SELECT_EPSILON)
    return false;

    d = fabs(tu1 - tu2);
    if (d > SELECT_TEXTURE)
    return false;

    d = fabs(tv1 - tv2);
    if (d > SELECT_TEXTURE)
    return false;

    return true;
}

void
Surface::OptimizeMesh()
{
    if (!vertex_set || vertex_set->nverts < 3)
    return;

    int nverts = vertex_set->nverts;
    int used   = 0;
    int final  = 0;
    int nmatch = 0;

    // create vertex maps:
    BYTE* vert_map = new BYTE[nverts];
    WORD* vert_dst = new WORD[nverts];
    ZeroMemory(vert_map, nverts * sizeof(BYTE));
    ZeroMemory(vert_dst, nverts * sizeof(WORD));

    // count used verts:
    for (int i = 0; i < npolys; i++) {
        Poly* p = polys + i;

        for (int j = 0; j < p->nverts; j++) {
            WORD vert  = p->verts[j];

            if (vert < nverts) {
                vert_map[vert]++;
                used++;
            }
        }
    }

    // create target vertex set:
    VertexSet*  vset = new(__FILE__,__LINE__) VertexSet(used);
    int v = 0;

    // compress verts:
    for (int i = 0; i < nverts; i++) {
        if (vert_map[i] == 0) continue;

        vert_dst[i]       = v;
        vset->loc[v]      = vertex_set->loc[i];
        vset->nrm[v]      = vertex_set->nrm[i];
        vset->tu[v]       = vertex_set->tu[i];
        vset->tv[v]       = vertex_set->tv[i];
        vset->rw[v]       = vertex_set->rw[i];
        vset->diffuse[v]  = vertex_set->diffuse[i];
        vset->specular[v] = vertex_set->specular[i];

        for (int j = i+1; j < nverts; j++) {
            if (vert_map[j] == 0) continue;

            if (MatchVerts(vertex_set, i, j)) {
                vert_map[j] = 0;
                vert_dst[j] = v;
                nmatch++;
            }
        }

        v++;
    }

    final = v;

    // remap polys:
    for (int n = 0; n < npolys; n++) {
        Poly* p = polys + n;
        p->vertex_set = vset;
        for (int v = 0; v < p->nverts; v++) {
            p->verts[v] = vert_dst[ p->verts[v] ];
        }
    }

    // finalize:
    if (vset && final < nverts) {
        delete vertex_set;
        vertex_set = vset;
        vset->Resize(final, true);
        nverts = final;
    }

    // clean up and rebuild hull:
    delete [] vert_map;

    if (vloc)
    delete [] vloc;

    vloc = new(__FILE__,__LINE__) Vec3[nverts];

    ComputeTangents();
    BuildHull();
}

// +--------------------------------------------------------------------+

void
Surface::ScaleBy(double factor)
{
    offset *= factor;

    if (vertex_set && vertex_set->nverts) {
        for (int i = 0; i < vertex_set->nverts; i++) {
            vertex_set->loc[i] *= (float) factor;
        }
    }
}

// +--------------------------------------------------------------------+

void
Surface::BuildHull()
{
    if (npolys < 1 || !vertex_set || vertex_set->nverts < 1)
    return;

    nhull = 0;

    for (int i = 0; i < npolys; i++) {
        Poly* p = polys + i;

        for (int n = 0; n < p->nverts; n++) {
            WORD v = p->verts[n];
            WORD h;

            for (h = 0; h < nhull; h++) {
                Vec3&  vl  = vertex_set->loc[v];
                Vec3&  loc = vloc[h];

                double d  = vl.x - loc.x;

                if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
                continue;

                d = vl.y - loc.y;

                if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
                continue;

                d = vl.z - loc.z;

                if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
                continue;

                // found a match:
                break;
            }

            // didn't find a match:
            if (h >= nhull) {
                vloc[h] = vertex_set->loc[v];
                nhull = h+1;
            }

            p->vlocs[n] = h;
        }
    }

    if (use_collision_detection)
    InitializeCollisionHull();
}

// +--------------------------------------------------------------------+

void
Surface::Normalize()
{
    if (npolys < 1 || !vertex_set || vertex_set->nverts < 1)
    return;

    // STEP ONE: initialize poly planes

    for (int i = 0; i < npolys; i++) {
        Poly* p = polys + i;

        p->plane = Plane( vertex_set->loc[ p->verts[0] ],
        vertex_set->loc[ p->verts[2] ],
        vertex_set->loc[ p->verts[1] ] );
    }

    // STEP TWO: compute vertex normals by averaging adjecent poly planes

    List<Poly> faces;
    for (int v = 0; v < vertex_set->nverts; v++) {
        faces.clear();
        SelectPolys(faces, vertex_set->loc[v]);

        if (faces.size()) {
            vertex_set->nrm[v] = Vec3(0.0f, 0.0f, 0.0f);

            for (int i = 0; i < faces.size(); i++) {
                vertex_set->nrm[v] += faces[i]->plane.normal;
            }

            vertex_set->nrm[v].Normalize();
        }

        else if (vertex_set->loc[v].length() > 0) {
            vertex_set->nrm[v] = vertex_set->loc[v];
            vertex_set->nrm[v].Normalize();
        }

        else {
            vertex_set->nrm[v] = Vec3(0.0f, 1.0f, 0.0f);
        }
    }

    // STEP THREE: adjust vertex normals for poly flatness

    for (int i = 0; i < npolys; i++) {
        Poly* p = polys + i;

        for (int n = 0; n < p->nverts; n++) {
            int v = p->verts[n];

            vertex_set->nrm[v] = vertex_set->nrm[v] * (1.0f - p->flatness) +
            p->plane.normal    * (       p->flatness);
        }
    }
}

void
Surface::SelectPolys(List<Poly>& selection, Vec3 loc)
{
    const double SELECT_EPSILON = 0.05;

    for (int i = 0; i < npolys; i++) {
        Poly* p = polys + i;

        for (int n = 0; n < p->nverts; n++) {
            int    v  = p->verts[n];
            Vec3&  vl = vertex_set->loc[v];
            double d  = vl.x - loc.x;

            if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
            continue;

            d = vl.y - loc.y;

            if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
            continue;

            d = vl.z - loc.z;

            if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
            continue;

            selection.append(p);
            break;
        }
    }
}

void
Surface::SelectPolys(List<Poly>& selection, Material* m)
{
    for (int i = 0; i < npolys; i++) {
        Poly* p = polys + i;

        if (p->material == m)
        selection.append(p);
    }
}

// +--------------------------------------------------------------------+

void
Surface::ComputeTangents()
{
    Vec3  tangent;
    Vec3  binormal;

    if (!vertex_set || !vertex_set->nverts)
    return;

    if (vertex_set->tangent)
    return;

    vertex_set->CreateTangents();

    for (int i = 0; i < npolys; i++) {
        Poly* p = polys + i;

        CalcGradients(*p, tangent, binormal);

        for (int n = 0; n < p->nverts; n++) {
            vertex_set->tangent[p->verts[n]]    = tangent;
            vertex_set->binormal[p->verts[n]]   = binormal;
        }
    }
}

void
Surface::CalcGradients(Poly& p, Vec3& tangent, Vec3& binormal)
{
    // using Eric Lengyel's approach with a few modifications
    // from Mathematics for 3D Game Programmming and Computer Graphics
    // want to be able to trasform a vector in Object Space to Tangent Space
    // such that the x-axis cooresponds to the 's' direction and the
    // y-axis corresponds to the 't' direction, and the z-axis corresponds
    // to <0,0,1>, straight up out of the texture map

    VertexSet* vset = p.vertex_set;

    Vec3  P  = vset->loc[p.verts[1]] - vset->loc[p.verts[0]];
    Vec3  Q  = vset->loc[p.verts[2]] - vset->loc[p.verts[0]];

    float s1 = vset->tu[p.verts[1]]  - vset->tu[p.verts[0]];
    float t1 = vset->tv[p.verts[1]]  - vset->tv[p.verts[0]];
    float s2 = vset->tu[p.verts[2]]  - vset->tu[p.verts[0]];
    float t2 = vset->tv[p.verts[2]]  - vset->tv[p.verts[0]];

    float tmp   = 1.0f;
    float denom = s1*t2 - s2*t1;

    if (fabsf(denom) > 0.0001f)
    tmp = 1.0f/(denom);

    tangent.x   = (t2*P.x - t1*Q.x) * tmp;
    tangent.y   = (t2*P.y - t1*Q.y) * tmp;
    tangent.z   = (t2*P.z - t1*Q.z) * tmp;

    tangent.Normalize();

    binormal.x  = (s1*Q.x - s2*P.x) * tmp;
    binormal.y  = (s1*Q.y - s2*P.y) * tmp;
    binormal.z  = (s1*Q.z - s2*P.z) * tmp;

    binormal.Normalize();
}

void
Surface::InitializeCollisionHull()
{
    opcode = new(__FILE__,__LINE__) OPCODE_data(this);
}

// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+

Segment::Segment()
{
    ZeroMemory(this, sizeof(Segment));
}

Segment::Segment(int n, Poly* p, Material* mtl, Model* mod)
    : npolys(n), polys(p), material(mtl), model(mod), video_data(0)
{
}

Segment::~Segment()
{
    delete video_data;

    ZeroMemory(this, sizeof(Segment));
}

// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+

ModelFile::ModelFile(const char* fname)
    : model(0), pname(0), pnverts(0), pnpolys(0), pradius(0)
{
    int len = sizeof(filename);
    ZeroMemory(filename, len);
    strncpy_s(filename, fname, len);
    filename[len-1] = 0;
}

ModelFile::~ModelFile()
{
}

bool
ModelFile::Load(Model* m, double scale)
{
    model = m;

    // expose model innards for child classes:

    if (model) {
        pname    = model->name;
        pnverts  = &model->nverts;
        pnpolys  = &model->npolys;
        pradius  = &model->radius;
    }

    return false;
}

bool
ModelFile::Save(Model* m)
{
    model = m;

    // expose model innards for child classes:

    if (model) {
        pname    = model->name;
        pnverts  = &model->nverts;
        pnpolys  = &model->npolys;
        pradius  = &model->radius;
    }

    return false;
}

